﻿using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Effects;

namespace Black.SurfaceToolkit.Effects
{
    /// <summary>
    /// MagnifyEffect Effect
    /// </summary>
    public class MagnifyEffect : ShaderEffect
    {
        #region Propriétées de dépendances

        public static readonly DependencyProperty RadiiProperty = DependencyProperty.Register("Radii", typeof(Size), typeof(MagnifyEffect), new UIPropertyMetadata(new Size(0.2, 0.2), ShaderEffect.PixelShaderConstantCallback(0)));
        public static readonly DependencyProperty CenterProperty = DependencyProperty.Register("Center", typeof(Point), typeof(MagnifyEffect), new UIPropertyMetadata(new Point(0.25, 0.25), ShaderEffect.PixelShaderConstantCallback(1)));
        public static readonly DependencyProperty ShrinkFactorProperty = DependencyProperty.Register("ShrinkFactor", typeof(double), typeof(MagnifyEffect), new UIPropertyMetadata(0.5, ShaderEffect.PixelShaderConstantCallback(2)));
        public static readonly DependencyProperty InputProperty = ShaderEffect.RegisterPixelShaderSamplerProperty("Input", typeof(MagnifyEffect), 0);

        #endregion

        #region Variables statiques

        private static readonly PixelShader _PixelShader = new PixelShader();

        #endregion

        #region Variables d'instances

        private MagnifyGeneralTransform m_oGeneralTransform;

        #endregion

        #region Constructeurs

        /// <summary>
        /// Constructeur statique
        /// </summary>
        static MagnifyEffect()
        {
            MagnifyEffect._PixelShader.UriSource = new Uri("pack://application:,,,/Black.SurfaceToolkit;component/Effects/Shaders/Magnify.ps");
        }

        /// <summary>
        /// Constructeur de la classe
        /// </summary>
        public MagnifyEffect()
        {
            this.PixelShader = MagnifyEffect._PixelShader;

            this.UpdateShaderValue(MagnifyEffect.RadiiProperty);
            this.UpdateShaderValue(MagnifyEffect.CenterProperty);
            this.UpdateShaderValue(MagnifyEffect.ShrinkFactorProperty);
            this.UpdateShaderValue(MagnifyEffect.InputProperty);

            this.m_oGeneralTransform = new MagnifyGeneralTransform(this);
        }

        #endregion

        #region Accesseurs

        /// <summary>
        /// Affecte ou retourne la propriété Input du Shader
        /// </summary>
        [System.ComponentModel.BrowsableAttribute(false)]
        public Brush Input
        {
            get
            {
                return ((Brush)this.GetValue(MagnifyEffect.InputProperty));
            }
            set
            {
                this.SetValue(MagnifyEffect.InputProperty, value);
            }
        }

        /// <summary>
        /// Affecte ou retourne la propriété Center du Shader
        /// </summary>
        public Point Center
        {
            get
            {
                return ((Point)this.GetValue(MagnifyEffect.CenterProperty));
            }
            set
            {
                this.SetValue(MagnifyEffect.CenterProperty, value);
            }
        }

        /// <summary>
        /// Retourne la propriété GeneralTransform du Shader
        /// </summary>
        protected override GeneralTransform EffectMapping
        {
            get
            {
                return (this.m_oGeneralTransform);
            }
        }

        /// <summary>
        /// Affecte ou retourne la propriété Radii du Shader
        /// </summary>
        public Size Radii
        {
            get
            {
                return ((Size)this.GetValue(MagnifyEffect.RadiiProperty));
            }
            set
            {
                this.SetValue(MagnifyEffect.RadiiProperty, value);
            }
        }

        /// <summary>
        /// Affecte ou retourne la propriété ShrinkFactor du Shader
        /// </summary>
        public double ShrinkFactor
        {
            get
            {
                return ((double)this.GetValue(MagnifyEffect.ShrinkFactorProperty));
            }
            set
            {
                this.SetValue(MagnifyEffect.ShrinkFactorProperty, value);
            }
        }

        #endregion

        #region Classes internes

        /// <summary>
        /// Cette classe corresopond aux spécifications de cet effet
        /// </summary>
        private class MagnifyGeneralTransform : GeneralTransform
        {
            #region Accesseurs

            /// <summary>
            /// Retourne la propriété Inverse
            /// </summary>
            public override GeneralTransform Inverse
            {
                get
                {
                    if (this.m_oInverseTransform == null)
                    {
                        this.m_oInverseTransform = (MagnifyGeneralTransform)this.Clone();
                        this.m_oInverseTransform.m_bThisIsInverse = !this.m_bThisIsInverse;
                    }

                    return (this.m_oInverseTransform);
                }
            }

            #endregion

            #region Variables d'instances

            private bool m_bThisIsInverse;
            private readonly MagnifyEffect m_oEffect;
            private MagnifyGeneralTransform m_oInverseTransform;

            #endregion

            #region Constructeurs

            /// <summary>
            /// Constructeur de la classe
            /// </summary>
            /// <param name="p_oFX">La source de l'effet</param>
            public MagnifyGeneralTransform(MagnifyEffect p_oFX)
            {
                this.m_oEffect = p_oFX;
            }

            #endregion

            #region Fonctions publiques

            /// <summary>
            /// Cet effet garde les axes alignés, donc la transformation du rectangle est juste la transformation des coins
            /// </summary>
            /// <param name="p_oRect">Rectangle d'entré</param>
            /// <returns>Rectangle de sortie</returns>
            public override Rect TransformBounds(Rect p_oRect)
            {
                Point oTL;
                Point oBR;
                Rect oResult;

                bool bOk1 = this.TryTransform(new Point(p_oRect.Left, p_oRect.Top), out oTL);
                bool bOk2 = this.TryTransform(new Point(p_oRect.Right, p_oRect.Bottom), out oBR);

                if (bOk1 && bOk2)
                    oResult = new Rect(oTL, oBR);
                else
                    oResult = Rect.Empty;

                return (oResult);
            }

            /// <summary>
            /// Essaye de transformer un point sur l'effet
            /// </summary>
            /// <param name="p_oTargetPoint">Le point a transformer</param>
            /// <param name="p_oResult">Le résultat si succès</param>
            /// <returns>TRUE si succès, FALSE sinon</returns>
            public override bool TryTransform(Point p_oTargetPoint, out Point p_oResult)
            {
                if (!this.PointIsInEllipse(p_oTargetPoint, this.m_oEffect.Center, this.m_oEffect.Radii))
                    p_oResult = p_oTargetPoint;
                else
                {
                    Point oCenter = this.m_oEffect.Center;
                    Point oRay = new Point(p_oTargetPoint.X - oCenter.X, p_oTargetPoint.Y - oCenter.Y);

                    double dScaleFactor = this.m_bThisIsInverse ? this.m_oEffect.ShrinkFactor : 1.0 / this.m_oEffect.ShrinkFactor;

                    p_oResult = new Point(oCenter.X + dScaleFactor * oRay.X, oCenter.Y + dScaleFactor * oRay.Y);
                }

                return (true);
            }

            #endregion

            #region Fonctions protégées

            /// <summary>
            /// Crée une instance
            /// </summary>
            /// <returns>Référcence de celle-ci</returns>
            protected override Freezable CreateInstanceCore()
            {
                return (new MagnifyGeneralTransform(this.m_oEffect)
                {
                    m_bThisIsInverse = this.m_bThisIsInverse
                });
            }

            #endregion

            #region Fonctions privées

            /// <summary>
            /// Détermine si un point est dans l'ellipse
            /// </summary>
            /// <param name="p_oPoint">Le point de test</param>
            /// <param name="p_oCenter">Le centre de l'ellipse</param>
            /// <param name="p_oRadius">Le radius de l'ellipse</param>
            /// <returns>TRUE si succès, FALSE sinon</returns>
            private bool PointIsInEllipse(Point p_oPoint, Point p_oCenter, Size p_oRadius)
            {
                Point oRay = new Point(p_oPoint.X - p_oCenter.X, p_oPoint.Y - p_oCenter.Y);

                double dRayPctX = oRay.X / p_oRadius.Width;
                double dRayPctY = oRay.Y / p_oRadius.Height;

                double dPctLength = dRayPctX * dRayPctX + dRayPctY * dRayPctY;

                return (dPctLength <= 1.0);
            }

            #endregion
        }

        #endregion
    }
}
